﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


using System;
using System.Collections.Generic;
using Tesla.Collections;
using Tesla.Content;
using Tesla.Core;
using Tesla.Direct3D10.Graphics.Implementation;
using Tesla.Graphics;
using Tesla.Graphics.Implementation;
using D3D = SlimDX.Direct3D10;
using D3DC = SlimDX.D3DCompiler;
using DXGI = SlimDX.DXGI;

namespace Tesla.Direct3D10.Graphics {
    /// <summary>
    /// Direct3D10 render system.
    /// </summary>
    public sealed class D3D10RenderSystemProvider : IRenderSystemProvider {
        private D3D10Renderer _renderer;
        private ContentManager _content;
        private long _idCount = 0;
        private ReadOnlyList<IGraphicsAdapter> _adapters;

        /// <summary>
        /// Gets a description of the render system's platform.
        /// </summary>
        public String Platform {
            get {
                return "Direct3D_10_0";
            }
        }

        /// <summary>
        /// Gets the renderer that was created by the render system.
        /// </summary>
        public IRenderer Renderer {
            get {
                return _renderer;
            }
        }

        /// <summary>
        /// Gets the default content manager which provides access to platform-specific content.
        /// </summary>
        public ContentManager DefaultContent {
            get {
                return _content;
            }
        }

        /// <summary>
        /// Gets the default graphics adapter.
        /// </summary>
        public IGraphicsAdapter DefaultAdapter {
            get {
                return _adapters[0];
            }
        }

        /// <summary>
        /// Gets all the adapters supported by ths system.
        /// </summary>
        public ReadOnlyList<IGraphicsAdapter> Adapters {
            get {
                return _adapters;
            }
        }

        /// <summary>
        /// Creates a new instance of <see cref="D3D10RenderSystemProvider"/> using the adapter index.
        /// </summary>
        /// <param name="adapterIndex">Index of the adapter to use when creating the underlying device</param>
        public D3D10RenderSystemProvider(int adapterIndex) {
            //Create the DXGI Factory
            DXGI.Factory factory = new DXGI.Factory();

            //Create the device
            DXGI.Adapter adapter = factory.GetAdapter(adapterIndex);
            D3D.Device device = new D3D.Device(adapter, D3D.DriverType.Hardware, D3D.DeviceCreationFlags.None);

            //Enumerate adapters
            List<IGraphicsAdapter> adapterList = new List<IGraphicsAdapter>();
            int adapterCount = factory.GetAdapterCount();
            for(int i = 0; i < adapterCount; i++) {
                adapterList.Add(new D3D10GraphicsAdapter(device, factory, i));
            }
            _adapters = new ReadOnlyList<IGraphicsAdapter>(adapterList);

            //Create the renderer
            _renderer = new D3D10Renderer(factory, device, (D3D10GraphicsAdapter) adapterList[adapterIndex]);

            //Create default content manager
            _content = new ContentManager(new EmbeddedResourceLocator(Tesla.Direct3D10.DefaultContent.ResourceManager));
            _content.UseDefaultContent = false;
        }

        /// <summary>
        /// Creates a new instance of <see cref="D3D10RenderSystemProvider"/> using the default adapter (index of zero).
        /// </summary>
        public D3D10RenderSystemProvider() : this(0) {}

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="D3D10RenderSystemProvider"/> is reclaimed by garbage collection.
        /// </summary>
        ~D3D10RenderSystemProvider() {
            Dispose(false);
        }

        /// <summary>
        /// Enumerates all the available adapters for the render system. This should
        /// only be used to identify which adapter to use at startup.
        /// </summary>
        /// <returns>List of available adapters</returns>
        public static ReadOnlyList<IGraphicsAdapter> EnumerateAdapters() {
            //Create the DXGI Factory
            DXGI.Factory factory = new DXGI.Factory();

            //Create the device
            DXGI.Adapter adapter = factory.GetAdapter(0);
            D3D.Device device = new D3D.Device(adapter, D3D.DriverType.Hardware, D3D.DeviceCreationFlags.None);

            //Enumerate adapters
            List<IGraphicsAdapter> adapterList = new List<IGraphicsAdapter>();
            int adapterCount = factory.GetAdapterCount();
            for(int i = 0; i < adapterCount; i++) {
                adapterList.Add(new D3D10GraphicsAdapter(device, factory, i));
            }

            factory.Dispose();
            return new ReadOnlyList<IGraphicsAdapter>(adapterList);
        }

        /// <summary>
        /// Creates a new swap chain implementation.
        /// </summary>
        /// <param name="windowHandle">Pointer to the window</param>
        /// <param name="presentParams">Presentation parameters</param>
        /// <returns>
        /// Swap chain implemented
        /// </returns>
        public SwapChainImplementation CreateSwapChainImplementation(IntPtr windowHandle, PresentationParameters presentParams) {
            D3D10SwapChainImplementation impl = new D3D10SwapChainImplementation(_renderer, _renderer.DXGIFactory, windowHandle, presentParams);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new blend state implementation.
        /// </summary>
        /// <returns>
        /// Blend state implementation
        /// </returns>
        public BlendStateImplementation CreateBlendStateImplementation() {
            D3D10BlendStateImplementation impl = new D3D10BlendStateImplementation(_renderer);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new depth stencil state implementation.
        /// </summary>
        /// <returns>
        /// Depth stencil implementation
        /// </returns>
        public DepthStencilStateImplementation CreateDepthStencilStateImplementation() {
            D3D10DepthStencilStateImplementation impl = new D3D10DepthStencilStateImplementation(_renderer);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new sampler state implementation.
        /// </summary>
        /// <returns>
        /// Sampler state implementation
        /// </returns>
        public SamplerStateImplementation CreateSamplerStateImplementation() {
            D3D10SamplerStateImplementation impl = new D3D10SamplerStateImplementation(_renderer);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new rasterizer state implementation.
        /// </summary>
        /// <returns>
        /// Rasterizer state implementation
        /// </returns>
        public RasterizerStateImplementation CreateRasterizerStateImplementation() {
            D3D10RasterizerStateImplementation impl = new D3D10RasterizerStateImplementation(_renderer);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new vertex buffer implementation.
        /// </summary>
        /// <param name="declaration">Declaration to use</param>
        /// <param name="vertexCount">Number of vertices</param>
        /// <param name="usage">Resource usage</param>
        /// <returns>
        /// Vertex buffer implementation
        /// </returns>
        public VertexBufferImplementation CreateVertexBufferImplementation(VertexDeclaration declaration, int vertexCount, ResourceUsage usage) {
            D3D10VertexBufferImplementation impl = new D3D10VertexBufferImplementation(_renderer, declaration, vertexCount, usage);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new vertex buffer implementation populated with the specified interleaved buffer.
        /// </summary>
        /// <param name="declaration">Declaration to use</param>
        /// <param name="usage">Resource usage</param>
        /// <param name="data">Interleaved DataBuffer to copy from. Stride must match the vertex declaration.</param>
        /// <returns>
        /// Vertex buffer implementation
        /// </returns>
        public VertexBufferImplementation CreateVertexBufferImplementation(VertexDeclaration declaration, ResourceUsage usage, DataBuffer data) {
            D3D10VertexBufferImplementation impl = new D3D10VertexBufferImplementation(_renderer, declaration, usage, data);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new vertex buffer implementation populated with the array of data buffers, one buffer for each vertex element.
        /// These must match up with the defined vertex declaration elements and be of the same number.
        /// </summary>
        /// <param name="declaration">Declaration to use</param>
        /// <param name="usage">Resource usage</param>
        /// <param name="data">DataBuffers to copy from, one for each vertex element. Total stride and size of each element must match the vertex declaration</param>
        /// <returns></returns>
        public VertexBufferImplementation CreateVertexBufferImplementation(VertexDeclaration declaration, ResourceUsage usage, params DataBuffer[] data) {
            D3D10VertexBufferImplementation impl = new D3D10VertexBufferImplementation(_renderer, declaration, usage, data);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new index buffer implementation.
        /// </summary>
        /// <param name="format">Index format, 16 or 32 bits.</param>
        /// <param name="indexCount">Number of indices</param>
        /// <param name="usage">Resource usage</param>
        /// <returns>
        /// Index buffer implementation
        /// </returns>
        public IndexBufferImplementation CreateIndexBufferImplementation(IndexFormat format, int indexCount, ResourceUsage usage) {
            D3D10IndexBufferImplementation impl = new D3D10IndexBufferImplementation(_renderer, format, indexCount, usage);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new index buffer implementation populated with the specified Int32 data.
        /// </summary>
        /// <param name="data">DataBuffer to copy from</param>
        /// <param name="usage">Resource usage</param>
        /// <returns>
        /// Index buffer implementation
        /// </returns>
        public IndexBufferImplementation CreateIndexBufferImplementation(DataBuffer<int> data, ResourceUsage usage) {
            D3D10IndexBufferImplementation impl = new D3D10IndexBufferImplementation(_renderer, data, usage);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new index buffer implementation popualted with the specified Int16 data.
        /// </summary>
        /// <param name="data">DataBuffer to copy from</param>
        /// <param name="usage">Resource usage</param>
        /// <returns>
        /// Index buffer implementation
        /// </returns>
        public IndexBufferImplementation CreateIndexBufferImplementation(DataBuffer<short> data, ResourceUsage usage) {
            D3D10IndexBufferImplementation impl = new D3D10IndexBufferImplementation(_renderer, data, usage);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new texture 1D implementation.
        /// </summary>
        /// <param name="width">Width of the texture</param>
        /// <param name="genMipmap">True if mipmaps should be generated</param>
        /// <param name="format">Surface format</param>
        /// <param name="data">DataBuffer to populate from, if null an empty texture and miplevels are generated.</param>
        /// <returns>
        /// Texture 1D implementation
        /// </returns>
        public Texture1DImplementation CreateTexture1DImplementation(int width, bool genMipmap, SurfaceFormat format, DataBuffer data) {
            D3D10Texture1DImplementation impl = new D3D10Texture1DImplementation(_renderer, width, genMipmap, format, data);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new texture 2D implementation.
        /// </summary>
        /// <param name="width">Width of the texture</param>
        /// <param name="height">Height of the texture</param>
        /// <param name="genMipmap">True if mipmaps should be generated</param>
        /// <param name="format">Surface format</param>
        /// <param name="data">DataBuffer to populate from, if null an empty texture is generated.</param>
        /// <returns>
        /// Texture 2D implementation
        /// </returns>
        public Texture2DImplementation CreateTexture2DImplementation(int width, int height, bool genMipmap, SurfaceFormat format, DataBuffer data) {
            D3D10Texture2DImplementation impl = new D3D10Texture2DImplementation(_renderer, width, height, genMipmap, format, data);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new texture 3D implementation.
        /// </summary>
        /// <param name="width">Width of the texture</param>
        /// <param name="height">Height of the texture</param>
        /// <param name="depth">Depth of the texture</param>
        /// <param name="genMipmap">True if mipmaps should be generated</param>
        /// <param name="format">Surface format</param>
        /// <param name="data">DataBuffer to populate from, if null an empty texture is generated.</param>
        /// <returns>
        /// Texture 3D implementation
        /// </returns>
        public Texture3DImplementation CreateTexture3DImplementation(int width, int height, int depth, bool genMipmap, SurfaceFormat format, DataBuffer data) {
            D3D10Texture3DImplementation impl = new D3D10Texture3DImplementation(_renderer, width, height, depth, genMipmap, format, data);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new texture cube implementation.
        /// </summary>
        /// <param name="size">Size (width/height) of the cube texture</param>
        /// <param name="genMipmap">True if mipmaps should be generated</param>
        /// <param name="format">Surface format</param>
        /// <param name="data">DataBuffer to populate from, if null an empty texture is generated.</param>
        /// <returns>
        /// Texture cube implementation
        /// </returns>
        public TextureCubeImplementation CreateTextureCubeImplementation(int size, bool genMipmap, SurfaceFormat format, DataBuffer[] data) {
            D3D10TextureCubeImplementation impl = new D3D10TextureCubeImplementation(_renderer, size, genMipmap, format, data);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new render target 2D implementation. All render targets are themselves textures.
        /// </summary>
        /// <param name="width">Width of the target</param>
        /// <param name="height">Height of the target</param>
        /// <param name="genMipMaps">True if mipmaps should be generated</param>
        /// <param name="format">Surface format</param>
        /// <param name="depthFormat">Depth format. For Multi-RenderTarget (MRT) usage, the depth buffer from the first render target
        /// set on the device will be used for all proceeding targets assigned.</param>
        /// <param name="multiSampleCount">Multisample count</param>
        /// <param name="usage">The target usage.</param>
        /// <returns>Render target 2D implementation</returns>
        public Texture2DImplementation CreateRenderTarget2DImplementation(int width, int height, bool genMipMaps, SurfaceFormat format, DepthFormat depthFormat, int multiSampleCount, RenderTargetUsage usage) {
            D3D10Texture2DImplementation impl = new D3D10Texture2DImplementation(_renderer, width, height, genMipMaps, format, depthFormat, multiSampleCount, usage);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new render target Cube implementation. All render targets are themselves textures.
        /// </summary>
        /// <param name="size">Size of the target (width/height are the same)</param>
        /// <param name="genMipMaps">True if mipmaps should be generated</param>
        /// <param name="format">Surface format</param>
        /// <param name="depthFormat">Depth format. For Multi-RenderTarget (MRT) usage, the depth buffer from the first render target
        /// set on the device will be used for all proceeding targets assigned.</param>
        /// <param name="multiSampleCount">Multisample count</param>
        /// <param name="usage">The target usage.</param>
        /// <returns>Render target Cube implementation</returns>
        public TextureCubeImplementation CreateRenderTargetCubeImplementation(int size, bool genMipMaps, SurfaceFormat format, DepthFormat depthFormat, int multiSampleCount, RenderTargetUsage usage) {
            D3D10TextureCubeImplementation impl = new D3D10TextureCubeImplementation(_renderer, size, genMipMaps, format, depthFormat, multiSampleCount, usage);
            SetGraphicsID(impl);
            return impl;
        }

        /// <summary>
        /// Creates a new effect implementation.
        /// </summary>
        /// <param name="shaderByteCode">Compiled shader byte code.</param>
        /// <returns>
        /// Effect implementation
        /// </returns>
        public EffectImplementation CreateEffectImplementation(byte[] shaderByteCode) {
            D3D10EffectImplementation impl = new D3D10EffectImplementation(_renderer, shaderByteCode, "fx_4_0", D3DC.ShaderFlags.None, D3DC.EffectFlags.None);
            SetGraphicsID(impl);
            return impl;
        }

        //Sets a resource tracking ID.
        private void SetGraphicsID(GraphicsResourceImplementation impl) {
            impl.ResourceID = _idCount;
            _idCount++;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        private void Dispose(bool disposing) {
            if(disposing) {
                _renderer.Dispose();
            }
        }
    }
}
